/*
This is part of Screenbar, which is based in part on the Litestep
shell source code.

Copyright (C) 2008-2009 Jim Babcock
Copyright (C) 1997-2009 The LiteStep Development Team

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
#include "stdafx.h"

LinearFlow::LinearFlow(string prefix)
	:FlowElement(prefix)
{
	lengthFirst = getConfigBool("LengthFirst", false, prefix.c_str());
	
	if(getConfigString("Vertical", "inherit", prefix.c_str()) == "inherit")
		flowDirection = flowDefault;
	else {
		if(getConfigBool("Vertical", prefix.c_str()))
			flowDirection = flowVertical;
		else
			flowDirection = flowHorizontal;
	}
	
	rowThickness = getConfigInt("RowThickness", 16, prefix.c_str());
	
	if(!_stricmp(getConfigString("Rows", "", prefix.c_str()).c_str(), "variable"))
	{
		variableRowCount = true;
		rowCount = -1;
	}
	else
	{
		variableRowCount = false;
		rowCount = getConfigInt("Rows", 1, prefix.c_str());
		if(rowCount < 1) {
			warn("Flow has an invalid number of rows\n");
			rowCount = 1;
		}
	}
}

LinearFlow::~LinearFlow()
{
}

ElementSize LinearFlow::getLength(const LayoutCacheNode *node)
{
	if(isCounterFlow(&node->context))
	{
		int rows = getRowCount(node);
		int rowThickness = getRowThickness(&node->context);
		int ret = rows*rowThickness;
		return ElementSize(ret, ret);
	}
	else
	{
		ElementSize ret;
		
		vector<int> rowAssignments;
		int rowCount = getRowCount(node);
		partitionRows(node->children, rowAssignments, rowCount, 0, false);
		
		ret.minimum = 0;
		int pos = 0;
		for(unsigned ii=0; ii<rowAssignments.size(); ii++)
		{
			int rowMinLength = 0;
			int rowPreferredLength = 0;
			
			for(int jj=pos; jj<pos+rowAssignments[ii]; jj++) {
				rowMinLength += node->children[jj]->size.minimum;
				rowPreferredLength += node->children[jj]->size.preferred;
			}
			
			if(ret.minimum < rowMinLength)
				ret.minimum = rowMinLength;
			if(ret.preferred < rowPreferredLength)
				ret.preferred = rowPreferredLength;
			
			pos += rowAssignments[ii];
		}
		
		if(lengthFirst)
		{
			ret.preferred = 0;
			for(unsigned ii=0; ii<node->children.size(); ii++)
				ret.preferred += node->children[ii]->size.preferred;
		}
		
		// TODO: Account for bounding rect
		
		return ret;
	}
}

int LinearFlow::getRowCount(const LayoutCacheNode *node)
{
	if(isCounterFlow(&node->context))
	{
		int rows = 1;
		int rowLength = node->context.thickness;
		int rowPos = 0;
		
		for(unsigned ii=0; ii<node->children.size(); ii++)
		{
			int elementMinSize = node->children[ii]->size.minimum;
			
			if(rowPos==0 || rowPos + elementMinSize <= rowLength) {
				rowPos += elementMinSize;
			} else {
				rowPos = 0;
				rows++;
			}
		}
		
		if(!rowPos && rows>1)
			rows--;
		
		return rows;
	}
	else
	{
		return getRowCount(&node->context);
	}
}

int LinearFlow::getRowCount(const ElementContext *context)
{
	if(variableRowCount)
		return max(1, context->thickness / rowThickness);
	else
		return rowCount;
}

bool LinearFlow::isVertical(const ElementContext *context)
{
	switch(flowDirection)
	{
		case flowHorizontal: return false;
		case flowVertical:   return true;
		default:
		case flowDefault:    return context->vertical;
	}
}

bool LinearFlow::isCounterFlow(const ElementContext *context)
{
	bool vertical = isVertical(context);
	return (vertical != context->vertical);
}

void LinearFlow::arrangeChildren(LayoutCacheNode *container, vector<LayoutCacheNode*> &children)
{
	int rowThickness = getRowThickness(&container->context);
	bool vertical = isVertical(&container->context);
	
	Rect boundingRect = getRect(container->boundingRect);
	
	int x = boundingRect.left;
	int y = boundingRect.top;
	int length = vertical ? boundingRect.height : boundingRect.width;
	
	vector<int> rowAssignments;
	partitionRows(children, rowAssignments, getRowCount(container), length, lengthFirst);
	
	int pos = 0;
	
	for(unsigned ii=0; ii<rowAssignments.size(); ii++)
	{
		int rowSize = rowAssignments[ii];
		vector<LayoutCacheNode*> rowChildren;
		rowChildren.insert(rowChildren.end(), children.begin()+pos, children.begin()+pos+rowSize);
		
		vector<int> lengths;
		flowRow(rowChildren, lengths, length);
		
		// Position children
		for(int jj=0; jj<rowSize; jj++)
		{
			LayoutCacheNode *element = children[pos+jj];
			
			element->boundingRect.left = x;
			element->boundingRect.top = y;
			
			if(vertical) {
				element->boundingRect.width = rowThickness;
				element->boundingRect.height = lengths[jj];
				y += lengths[jj];
			} else {
				element->boundingRect.height = rowThickness;
				element->boundingRect.width = lengths[jj];
				x += lengths[jj];
			}
		}
		if(vertical) {
			x += rowThickness;
			y = boundingRect.top;
		} else {
			y += rowThickness;
			x = boundingRect.left;
		}
		pos += rowSize;
	}
}

void LinearFlow::partitionRows(const vector<LayoutCacheNode*> &children, vector<int> &assignments, int rows, int rowLength, bool lengthFirst)
{
	if(rows==1) {
		assignments.push_back(children.size());
		return;
	}
	
	// There are two different ways to do this, depending on whether this flow
	// is allowed to shrink itself or not. In length-first mode, the second row
	// isn't used until the first is filled, the third isn't used until the
	// second is filled, etc. In regular mode, all rows are used, to minimize
	// the total width.
	if(lengthFirst || rows==1)
	{
		// TODO
		assignments.push_back(children.size());
	}
	else
	{
		// TODO: Make this take into account the sizes of elements, rather than
		// just the count
		int elementsPerRow = children.size() / rows;
		for(int ii=0; ii<rows; ii++)
			assignments.push_back(elementsPerRow);
		unsigned assigned = elementsPerRow*rows;
		for(unsigned ii=0; assigned<children.size(); assigned++, ii++)
			assignments[ii]++;
	}
}

void LinearFlow::flowRow(const vector<LayoutCacheNode*> &children, vector<int> &lengths, int rowLength)
{
	// Find the total size
	int totalMinLength = 0;
	int totalPreferredLength = 0;
	for(unsigned ii=0; ii<children.size(); ii++)
	{
		totalMinLength += children[ii]->size.minimum;
		totalPreferredLength += children[ii]->size.preferred;
	}
	
	int availableLength = rowLength;
	
	if(totalPreferredLength <= availableLength)
	{
		// There is more than enough space available, 
		for(unsigned ii=0; ii<children.size(); ii++)
			lengths.push_back(children[ii]->size.preferred);
		
		int excess = availableLength - totalPreferredLength;
		vector<int> expandingElements;
		for(unsigned ii=0; ii<children.size(); ii++)
		{
			if(children[ii]->element->isExpanding())
				expandingElements.push_back(ii);
		}
		if(expandingElements.size())
		{
			int expansion = excess / expandingElements.size();
			excess -= expansion*expandingElements.size();
			for(unsigned ii=0; ii<expandingElements.size(); ii++)
				lengths[expandingElements[ii]] += expansion;
			for(int ii=0; ii<min((int)expandingElements.size(), excess); ii++)
				lengths[expandingElements[ii]]++;
		}
	}
	else if(totalMinLength >= availableLength || totalMinLength==totalPreferredLength)
	{
		for(unsigned ii=0; ii<children.size(); ii++)
			lengths.push_back(children[ii]->size.minimum);
	}
	else
	{
		// There is some space available for children to be bigger than their
		// minimum size, but not enough to satisfy everyone's preferred size.
		// Give every child a size in between its min and pref making sure that
		//  - The full length is filled
		//  - Everything gets at least its minimum length
		//  - Lengths are as close to equal as possible
		set<int> childrenWantingMore;
		
		// First, give everything its minimum size, and note which elements
		// want more than that.
		for(unsigned ii=0; ii<children.size(); ii++)
		{
			lengths.push_back(children[ii]->size.minimum);
			if(children[ii]->size.preferred> children[ii]->size.minimum)
				childrenWantingMore.insert(ii);
		}
		
		int spaceLeft = availableLength - totalMinLength;
		
		while(spaceLeft > 0)
		{
			// Find the smallest requested expansion
			int smallestRequest = -1;
			for(set<int>::iterator ii=childrenWantingMore.begin(); ii!=childrenWantingMore.end(); ii++)
			{
				int request = children[*ii]->size.preferred - lengths[*ii];
				if(request<smallestRequest || smallestRequest<0)
					smallestRequest = request;
			}
			
			// If there is enough space to give smallestRequest to every child
			// that still wants more, do so.
			if(smallestRequest * (int)childrenWantingMore.size() <= spaceLeft)
			{
				spaceLeft -= smallestRequest * childrenWantingMore.size();
				
				for(set<int>::iterator ii=childrenWantingMore.begin(); ii!=childrenWantingMore.end(); )
				{
					lengths[*ii] += smallestRequest;
					if(lengths[*ii] == children[*ii]->size.preferred)
						ii = childrenWantingMore.erase(ii);
					else
						ii++;
				}
			}
			// There is not enough space left to satisfy the smallest request
			// for everything, so divide the remaining space among the children
			// that still want it
			else
			{
				// First give an equal amount to all
				int spaceGiven = (int)(spaceLeft / childrenWantingMore.size());
				for(set<int>::iterator ii=childrenWantingMore.begin(); ii!=childrenWantingMore.end(); ii++)
					lengths[*ii] += spaceGiven;
				spaceLeft -= spaceGiven * childrenWantingMore.size();
				// Give out the last few pixels of space, to have a clean right edge
				for(set<int>::iterator ii=childrenWantingMore.begin(); ii!=childrenWantingMore.end(); ii++) {
					lengths[*ii]++;
					spaceLeft--;
					if(spaceLeft<=0)
						break;
				}
			}
		}
	}
}

ElementContext LinearFlow::getChildContext(const ElementContext *parentContext)
{
	ElementContext ret = *parentContext;
	ret.vertical = isVertical(parentContext);
	ret.thickness = getRowThickness(parentContext);
	return ret;
}

int LinearFlow::getRowThickness(const ElementContext *context)
{
	if(isCounterFlow(context))
	{
		return rowThickness;
	}
	else
	{
		int rows = getRowCount(context);
		int thickness;
		
		if(context->vertical)
			thickness = ParseCoordinate(width.c_str(), context->thickness, context->thickness);
		else
			thickness = ParseCoordinate(height.c_str(), context->thickness, context->thickness);

		return thickness / rows;
	}
}
